package com.weilus.util.encrypt;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.apache.tomcat.util.codec.binary.Base64;

public class RSA{
	enum Crypt{
		ENCRYPT(117),//RSA最大加密大小
		DECRYPT(128);//RSA最大解密大小
		int block;
		Crypt(int block){
			this.block = block;
		}
	}

	/**
	 * 加密算法RSA
	 */
	public static final String KEY_ALGORITHM = "RSA";

	/**
	 * 签名算法
	 */
	public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

	/**
	 * 生成公私钥匙对
	 */
	public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		keyPairGen.initialize(1024);
		KeyPair keyPair = keyPairGen.generateKeyPair();
		return keyPair;
	}

	/**
	 * 公私钥匙对 ---> 写入文件
	 */
	public static void keyPair2File(KeyPair keyPair,String pri_file_path,String pub_file_path) throws IOException {
		File privateKeyFile = new File(pri_file_path),publicKeyFile = new File(pub_file_path);
		// Create files to store public and private key
		if (privateKeyFile.getParentFile() != null) {
			privateKeyFile.getParentFile().mkdirs();
		}
		privateKeyFile.createNewFile();
		if (publicKeyFile.getParentFile() != null) {
			publicKeyFile.getParentFile().mkdirs();
		}
		publicKeyFile.createNewFile();
		// Saving the Public key in a file
		try (ObjectOutputStream publicKeyOS = new ObjectOutputStream(new FileOutputStream(publicKeyFile))) {
			publicKeyOS.writeObject(keyPair.getPublic());
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Saving the Private key in a file
		try (ObjectOutputStream privateKeyOS = new ObjectOutputStream(new FileOutputStream(privateKeyFile))) {
			privateKeyOS.writeObject(keyPair.getPrivate());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 私钥签名
	 */
	public static String sign(byte[] data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateKey);
		signature.update(data);
		return new String(Base64.encodeBase64(signature.sign()));
	}

	/**
	 * 公钥验签名
	 */
	public static boolean verify(byte[] data, PublicKey publicKey, String sign) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicKey);
		signature.update(data);
		return signature.verify(Base64.decodeBase64(sign));
	}

	/**
	 * 得到私钥
	 * @param privateKey 私钥字符串（经过base64编码）
	 */
	public static PrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
		byte[] keyBytes = Base64.decodeBase64(privateKey);
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privatekey = keyFactory.generatePrivate(keySpec);
		return privatekey;
	}

	/**
	 * 得到私钥
	 * @param privateKey 私钥对象
	 */
	public static String getPrivateKey(PrivateKey privateKey){
		return new String(Base64.encodeBase64(privateKey.getEncoded()));
	}

	/**
	 * 得到私钥
	 * @param keyPair 钥匙对
	 */
	public static String getPrivateKey(KeyPair keyPair){
		return getPrivateKey(keyPair.getPrivate());
	}

	/**
	 * 得到公钥
	 * @param publicKey 公钥对象
	 */
	public static String getPublicKey(PublicKey publicKey){
		return new String(Base64.encodeBase64(publicKey.getEncoded()));
	}

	/**
	 * 得到公钥
	 * @param publicKey 公钥字符串（经过base64编码）
	 */
	public static PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
		byte[] keyBytes = Base64.decodeBase64(publicKey);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		return keyFactory.generatePublic(keySpec);
	}

	/**
	 * 得到公钥
	 * @param keyPair 钥匙对
	 */
	public static String getPublicKey(KeyPair keyPair){
		return getPublicKey(keyPair.getPublic());
	}

	/**
	 * 公、私钥 文件   得到文本公、私钥
	 * @param fis rsa_public.key 或 rsa_private.key 文件流
	 */
	public static String getKey(FileInputStream fis){
		try(ObjectInputStream key_is = new ObjectInputStream(fis)){
			Key key = (Key) key_is.readObject();
			return new String(Base64.encodeBase64(key.getEncoded()));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**解密*/
	public static byte[] decrypt(Key key,byte[] data)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, key);
		return crypt(cipher,data,Crypt.DECRYPT);
	}

	/**解密*/
	public static byte[] decryptPKCS1Padding(Key key,byte[] data)
			throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
		Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
		cipher.init(Cipher.DECRYPT_MODE, key);
		return crypt(cipher,data,Crypt.DECRYPT);
	}

	/**加密*/
	public static byte[] encrypt(Key key,byte[] data)
			throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
		Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, key);
		return crypt(cipher,data,Crypt.ENCRYPT);
	}

	/**加密*/
	public static byte[] encryptPKCS1Padding(Key key,byte[] data)
			throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
		Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
		cipher.init(Cipher.ENCRYPT_MODE, key);
		return crypt(cipher,data,Crypt.ENCRYPT);
	}

	// 对数据分段 加密 / 解密
	private static byte[] crypt(Cipher cipher,byte[] data,Crypt crypt) throws BadPaddingException, IllegalBlockSizeException {
		try (ByteArrayOutputStream out = new ByteArrayOutputStream()){
			int inputLen = data.length;
			int offSet = 0;
			byte[] cache;
			int i = 0;
			while (inputLen - offSet > 0) {
				if (inputLen - offSet > crypt.block) {
					cache = cipher.doFinal(data, offSet, crypt.block);
				} else {
					cache = cipher.doFinal(data, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * crypt.block;
			}
			return out.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}


	public static void main(String[] args) throws Exception {
		String rsaPubPath = "C:\\Users\\Administrator\\Desktop\\rsa_pub.key", rsaPriPath = "C:\\Users\\Administrator\\Desktop\\rsa_pri.key";
		if(Files.notExists(Paths.get(rsaPubPath)) || Files.notExists(Paths.get(rsaPriPath))) {
			KeyPair keyPair = genKeyPair();
			keyPair2File(keyPair, rsaPriPath, rsaPubPath);
		}
		String rsaPub = getKey(new FileInputStream(rsaPubPath)), rsaPri = getKey(new FileInputStream(rsaPriPath));
		Key publicKey = getPublicKey(rsaPub),privateKey = getPrivateKey(rsaPri);
		System.err.println("rsaPub:\n"+rsaPub);
		System.err.println("rsaPri:\n"+rsaPri);

		byte[] cipher = encrypt(publicKey,"ffasdfsdfdfas12312313aa".getBytes());
		byte[] plain = decrypt(privateKey,cipher);
		System.out.println(new String(plain));

		byte[] cipher1 = encryptPKCS1Padding(publicKey,"as;djfap89SDGSFDG@@#$@$@87FGHJF45$$".getBytes());
		byte[] plain1 = decryptPKCS1Padding(privateKey,cipher1);
		System.out.println(new String(plain1));
	}
}
